<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>大文件上传</title>
    <style>
      body {
        padding: 24px;
      }

      button {
        padding: 8px 16px;
        cursor: pointer;
        border: 1px solid #409eff;
        border-radius: 4px;
        background-color: #409eff;
        color: #fff;
        margin-right: 16px;
      }

      .progress {
        width: 100%;
        height: 24px;
        background-color: #ebeef5;
        border-radius: 12px;
        margin-bottom: 24px;
        overflow: hidden;
      }
      .progress-inner {
        width: 0%;
        height: 100%;
        background-color: #67c23a;
        border-radius: 12px;
        text-align: right;
        transition: width 0.6s ease;
      }
      .progress-text {
        color: #fff;
        margin: 0 5px;
        display: inline-block;
        font-size: 12px;
      }
    </style>
  </head>

  <body>
    <!-- 进度条 -->
    <div class="progress">
      <div class="progress-inner">
        <div class="progress-text"></div>
      </div>
    </div>
    <!-- 上传完显示预览地址 -->
    <p>
      <a id="filelink"></a>
    </p>
    <!-- 操作按钮 -->
    <button id="upload">
      <input type="file" id="file" style="display: none" />
      上传文件
    </button>
    <button id="download">下载文件</button>

    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/spark-md5@3.0.2/spark-md5.js"></script>
    <script>
      // 上传
      document.querySelector("#upload").addEventListener("click", () => {
        document.querySelector("#file").click();
      });
      // 选中文件直接上传
      document.querySelector("#file").addEventListener("change", async (e) => {
        const file = e.target.files[0];
        // 获取文件的所有分块
        const blobList = getFileSlices(file);
        const uploadRequests = [];
        // 所有分块的md5值
        // 后端根据md5值存的块，请求合并根据md5值进行合并
        const md5ChunkList = [];
        let totalUploaded = 0;

        for (let i = 0; i < blobList.length; i++) {
          const blob = blobList[i];

          const md5 = await calculateMD5(blob);
          md5ChunkList.push(md5);

          const formData = new FormData();
          formData.append("file", blob);
          formData.append("md5", md5);

          const request = axios({
            method: "post",
            url: "http://localhost:3000/upload",
            data: formData,
            onUploadProgress: (progressEvent) => {
              totalUploaded += progressEvent.loaded;
              let percentCompleted = Math.floor(
                (totalUploaded / file.size) * 100
              );
              // 为合并留1%进度条
              if (percentCompleted >= 100) {
                percentCompleted = 99;
              }
              updateProgressBar(percentCompleted);
            },
          });
          uploadRequests.push(request);
        }

        try {
          // 等待所有分块上传完成，再请求合并
          await Promise.all(uploadRequests);
          const { data } = await axios({
            method: "post",
            url: "http://localhost:3000/merge",
            data: { md5: md5ChunkList, fileName: file.name },
          });

          updateProgressBar(100);
          const url = `http://localhost:3000${data.data}`;
          console.log(url);

          const a = document.querySelector("#filelink");
          a.href = url;
          a.target = "_blank";
          a.innerText = url;
        } catch (e) {
          console.error(e);
        }
      });

      // 获取文件分块
      // 默认分块大小5m
      function getFileSlices(file, segmentSize = 5 * 1024 * 1024) {
        const fileSlices = [];
        let offset = 0;
        while (offset < file.size) {
          const segment = file.slice(offset, offset + segmentSize);
          fileSlices.push(segment);
          offset += segmentSize;
        }
        return fileSlices;
      }

      // 计算分块的md5值
      function calculateMD5(blob) {
        return new Promise((resolve, reject) => {
          const fileReader = new FileReader();
          let spark = new SparkMD5.ArrayBuffer();

          fileReader.onload = (e) => {
            spark.append(e.target.result);
            resolve(spark.end());
          };

          fileReader.onerror = () => {
            reject("blob读取失败");
          };

          fileReader.readAsArrayBuffer(blob);
        });
      }

      function updateProgressBar(percent) {
        const width = percent + "%";
        document.querySelector(".progress-inner").style.width = width;
        document.querySelector(".progress-text").innerText = width;
      }

      // 下载
      document.querySelector("#download").addEventListener("click", () => {
        const url = document.querySelector("#filelink").innerText;
        const fileName = url.substring(url.lastIndexOf("/") + 1);

        const download = document.createElement("a");
        download.href = `http://localhost:3000/download/${fileName}`;
        download.download = fileName;
        download.target = "_blank";
        download.click();
      });
    </script>
  </body>
</html>
